﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using Xceed.Silverlight.Data.Stats;

namespace Xceed.Silverlight.Data
{
  internal class DataSourceProviderEnumerator<T> : AsyncEnumerator<T> where T : RawDataItem
  {
    #region Static Fields

    private static readonly Guid JumpMoveAndFetchCallId = DataUtils.CreateCallId();
    private static readonly Guid JumpMoveAndFetchOneWayCallId = DataUtils.CreateCallId();
    private static readonly Guid NilCountCallId = DataUtils.CreateCallId();
    private static readonly Guid NilGroupCountCallId = DataUtils.CreateCallId();
    private static readonly Guid GetStatValueCallId = DataUtils.CreateCallId();

    #endregion

    #region Constructor

    internal DataSourceProviderEnumerator( DataSourceProvider dataSourceProvider )
    {
      if( dataSourceProvider == null )
        throw new ArgumentNullException( "dataSourceProvider" );

      m_dataSourceProvider = dataSourceProvider;
      m_isFullySynchronized = !dataSourceProvider.IsAsynchronous;

      //DataSourceChangedEventManager.AddListener( dataSourceProvider, this );
    }

    #endregion

    #region IsFullySynchronized Property

    public override bool IsFullySynchronized
    {
      get
      {
        return m_isFullySynchronized;
      }
    }

    private readonly bool m_isFullySynchronized;

    #endregion

    public override IAsyncResult BeginCount(
      CountParameters parameters,
      AsyncCallback callback,
      object state )
    {
      CountQueryParameters queryParameters;

      if( ( m_disposed ) || ( !this.CreateCountRequest( parameters, out queryParameters ) ) )
      {
        AsyncResult asyncResult = new AsyncResult( DataSourceProviderEnumerator<T>.NilCountCallId, callback, state );
        asyncResult.StartSync( null );

        return asyncResult;
      }
      else
      {
        AsyncCallback middleTierCallback = this.GetMiddleTierCallback( callback );

        return m_dataSourceProvider.BeginExecuteCountQueryInternal( queryParameters, middleTierCallback, state );
      }
    }

    public override CountResult EndCount( IAsyncResult asyncResult )
    {
      if( asyncResult.UseCallId( DataSourceProviderEnumerator<T>.NilCountCallId ) )
        return asyncResult.GetCallResult<CountResult>();

      CountQueryResult queryResult = m_dataSourceProvider.EndExecuteCountQueryInternal( asyncResult );

      if( queryResult == null )
        return null;

      return new CountResult( queryResult.Count );
    }

    public override IAsyncResult BeginGroupCount(
      CountParameters parameters,
      AsyncCallback callback,
      object state )
    {
      GroupCountQueryParameters queryParameters;

      if( ( m_disposed ) || ( !this.CreateGroupCountRequest( parameters, out queryParameters ) ) )
      {
        AsyncResult asyncResult = new AsyncResult( DataSourceProviderEnumerator<T>.NilGroupCountCallId, callback, state );
        asyncResult.StartSync( null );

        return asyncResult;
      }
      else
      {
        AsyncCallback middleTierCallback = this.GetMiddleTierCallback( callback );

        return m_dataSourceProvider.BeginExecuteGroupCountQueryInternal( queryParameters, middleTierCallback, state );
      }
    }

    public override GroupCountResult EndGroupCount( IAsyncResult asyncResult )
    {
      if( asyncResult.UseCallId( DataSourceProviderEnumerator<T>.NilGroupCountCallId ) )
        return asyncResult.GetCallResult<GroupCountResult>();

      GroupCountQueryResult queryResult = m_dataSourceProvider.EndExecuteGroupCountQueryInternal( asyncResult );

      if( queryResult == null )
        return null;

      IList<EstimateInt64> groupCounts = ( from count in queryResult.GroupCounts
                                           select new EstimateInt64( count ) ).ToList();

      return new GroupCountResult( groupCounts );
    }

    public override JumpMoveAndFetchAsyncResult BeginJumpMoveAndFetch(
      JumpMoveAndFetchParameters parameters,
      AsyncCallback callback,
      object state )
    {
      this.CheckMoveAndFetchParameters( parameters.ExpectedMoveCount, parameters.ExpectedFetchCount );

      AsyncCallback middleTierCallback = this.GetMiddleTierCallback( callback );
      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult(
                                                  DataSourceProviderEnumerator<T>.JumpMoveAndFetchCallId,
                                                  middleTierCallback,
                                                  state,
                                                  parameters );

      if( !this.HandleOutOfBoundMove( asyncResult, parameters ) )
        if( !this.HandleOneWayAction( asyncResult, parameters ) )
          throw new DataInternalException();

      return asyncResult;
    }

    public override JumpMoveAndFetchResult<T> EndJumpMoveAndFetch( JumpMoveAndFetchAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( DataSourceProviderEnumerator<T>.JumpMoveAndFetchCallId );

      return asyncResult.GetCallResult<JumpMoveAndFetchResult<T>>();
    }

    public override IAsyncResult BeginGetStatValues(
      StatParameters parameters,
      AsyncCallback callback,
      object state )
    {
      AsyncResult asyncResult = new AsyncResult(
                                  DataSourceProviderEnumerator<T>.GetStatValueCallId,
                                  this.GetMiddleTierCallback( callback ),
                                  state );

      this.HandleStatFunction( asyncResult, parameters );

      return asyncResult;
    }

    public override StatResults EndGetStatValues( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( DataSourceProviderEnumerator<T>.GetStatValueCallId );

      return asyncResult.GetCallResult<StatResults>();
    }

    protected new void CheckMoveAndFetchParameters( long expectedMoveCount, int expectedFetchCount )
    {
      base.CheckMoveAndFetchParameters( expectedMoveCount, expectedFetchCount );

      // Check if the move and the fetch part are done in the same direction.
      if( Math.Abs( Math.Sign( expectedMoveCount ) - Math.Sign( expectedFetchCount ) ) > 1 )
        throw new DataInternalException();
    }

    protected override IEnumerable<DataSourceChangedEventArgs> TransformDataSourceChangedEventArgs(
      DataSourceContext context,
      DataSourceChangedEventArgs e )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      if( e == null )
        throw new ArgumentNullException( "e" );

      lock( this.SyncRoot )
      {
        if( m_disposed )
          return Enumerable.Empty<DataSourceChangedEventArgs>();

        switch( e.Action )
        {
          case DataSourceChangedAction.Add:
            return this.TransformDataSourceChangedEventArgsItemsAdded( context.FilterContext, e );

          case DataSourceChangedAction.Remove:
            return this.TransformDataSourceChangedEventArgsItemsRemoved( context.FilterContext, e );

          case DataSourceChangedAction.ItemChanged:
            return this.TransformDataSourceChangedEventArgsItemsChanged( e );

          case DataSourceChangedAction.Replace:
            return this.TransformDataSourceChangedEventArgsItemsReplaced( context.FilterContext, e );

          case DataSourceChangedAction.Reset:
            return new DataSourceChangedEventArgs[] { e };

          default:
            throw new NotSupportedException();
        }
      }
    }

    private bool HandleOutOfBoundMove(
      JumpMoveAndFetchAsyncResult asyncResult,
      JumpMoveAndFetchParameters parameters )
    {
      SourcePosition sourcePosition = parameters.SourcePosition;

      // Make sure we are out of bound and we aren't moving in bound.
      if( ( parameters.ExpectedMoveCount <= 0 && sourcePosition.IsBeforeFirst )
        || ( parameters.ExpectedMoveCount >= 0 && sourcePosition.IsAfterLast ) )
      {
        ReferencePoint referencePoint = ( sourcePosition.IsBeforeFirst )
                                          ? ReferencePoint.BeforeFirst
                                          : ReferencePoint.AfterLast;
        JumpMoveAndFetchResult<T> result = new JumpMoveAndFetchResult<T>(
                                             asyncResult,
                                             0,
                                             null,
                                             false,
                                             sourcePosition,
                                             new RelativeOffset( 0, referencePoint ),
                                             null );
        asyncResult.StartSync( result );

        return true;
      }

      return false;
    }

    private bool HandleOneWayAction(
      JumpMoveAndFetchAsyncResult asyncResult,
      JumpMoveAndFetchParameters parameters )
    {
      // We must always fetch at least 1 item in order to keep a reference point.
      // That reference point will be use as a starting position for the next
      // operation.
      JumpMoveAndFetchParameters newParameters = parameters;
      int fetchCount = parameters.ExpectedFetchCount;
      if( parameters.ExpectedFetchCount == 0 )
      {
        fetchCount = ( Math.Sign( parameters.ExpectedMoveCount ) >= 0 ) ? 1 : -1;
        newParameters = parameters.CreateWithNewFetchCount( fetchCount );
      }

      // This anonymous method starts the one way action query.
      Func<AsyncCallback, object, IAsyncResult> begin =
        ( cb, st ) => this.BeginJumpMoveAndFetchOneWay( newParameters, cb, st );

      // This anonymous method returns the result of the one way action query.
      Func<AsyncResult, IAsyncResult, DependentCallResult> end =
        ( par, iar ) =>
        {
          JumpMoveAndFetchAsyncResult jar = ( JumpMoveAndFetchAsyncResult )iar;
          JumpMoveAndFetchResult<T> result = this.EndJumpMoveAndFetchOneWay( jar );

          // We must remove the extra item we have fetched.
          if( ( result != null ) && ( parameters.ExpectedFetchCount != newParameters.ExpectedFetchCount ) )
          {
            List<T> fetchedItems = ( result.Result ) ? new List<T>( 0 ) : null;

            return new DependentCallResult(
                      new JumpMoveAndFetchResult<T>(
                        ( JumpMoveAndFetchAsyncResult )par,
                        result.ActualMoveCount,
                        fetchedItems,
                        result.Result,
                        result.EndPositionObject,
                        result.EndPositionOffset,
                        result.TotalCount ) );
          }

          return new DependentCallResult( result );
        };

      asyncResult.Start( begin, end );

      return true;
    }

    private void HandleStatFunction(
      AsyncResult asyncResult,
      StatParameters parameters )
    {
      StatFunction[] statFunctions = parameters.StatFunctions.ToArray();
      object[] groupValues = parameters.GroupValues.ToArray();
      StatQueryParameters[] queriesParameters = new StatQueryParameters[ statFunctions.Length ];
      StatResult[] statResults = new StatResult[ statFunctions.Length ];
      DataSourceContext context = parameters.Context;
      bool isDisposed = m_disposed;

      // Find out the parameters to use to retrieve each stat values.
      for( int i = 0; i < statFunctions.Length; i++ )
      {
        StatQueryParameters queryParameters = default( StatQueryParameters );
        StatResult statResult = null;

        if( ( isDisposed )
          || ( ( groupValues.Length == 0 ) && ( !this.CreateStatRequest( context, statFunctions[ i ], out queryParameters ) ) )
          || ( ( groupValues.Length != 0 ) && ( !this.CreateStatRequest( context, statFunctions[ i ], groupValues, out queryParameters ) ) ) )
        {
          statResult = new StatResult( new NotSupportedStatFunctionException() );
        }

        queriesParameters[ i ] = queryParameters;
        statResults[ i ] = statResult;
      }

      // We already have a value for each stat function.
      if( ( statResults.Length == 0 )
        || ( statResults.All( ( statResult ) => ( statResult != null ) ) ) )
      {
        asyncResult.StartSync( new StatResults( statResults ) );

        return;
      }

      Func<AsyncCallback, object, IAsyncResult> invokeNextStatQuery =
        ( cb, st ) =>
        {
          // Retrieve the first StatFunction that hasn't a result yet.
          StatQueryParameters queryParameters = ( from item in statResults.Zip( queriesParameters, ( r, p ) => new Tuple<StatResult, StatQueryParameters>( r, p ) )
                                                  where item.Item1 == null
                                                  select item.Item2 ).First();

          return m_dataSourceProvider.BeginExecuteStatQueryInternal( queryParameters, cb, st );
        };

      Func<AsyncResult, IAsyncResult, DependentCallResult> retrieveStatResult = null;
      retrieveStatResult =
        ( par, iar ) =>
        {
          StatQueryResult queryResult = m_dataSourceProvider.EndExecuteStatQueryInternal( iar );
          StatResult statResult = ( queryResult != null )
                                    ? queryResult.Result
                                    : new StatResult( new NotSupportedStatFunctionException() );

          bool hasMore = false;
          bool resultAdded = false;

          for( int i = 0; i < statResults.Length; i++ )
          {
            if( statResults[ i ] == null )
            {
              if( !resultAdded )
              {
                statResults[ i ] = statResult;
                resultAdded = true;
              }
              else if( m_disposed )
              {
                statResults[ i ] = new StatResult( new NotSupportedStatFunctionException() );
              }
              else
              {
                hasMore = true;
                break;
              }
            }
          }

          if( hasMore )
            return new DependentCallResult( null, invokeNextStatQuery, retrieveStatResult );

          return new DependentCallResult( new StatResults( statResults ) );
        };

      asyncResult.Start( invokeNextStatQuery, retrieveStatResult );
    }

    private JumpMoveAndFetchAsyncResult BeginJumpMoveAndFetchOneWay(
      JumpMoveAndFetchParameters parameters,
      AsyncCallback callback,
      object state )
    {
      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult(
                                                  DataSourceProviderEnumerator<T>.JumpMoveAndFetchOneWayCallId,
                                                  callback,
                                                  state,
                                                  parameters );

      if( m_disposed )
      {
        asyncResult.StartSync( null );

        return asyncResult;
      }

      DataQueryParameters queryParameters;
      DataSourceContext context = parameters.Context;
      bool useNavigationFilter;

      if( !this.CreateDataRequest(
            context,
            parameters.SourcePosition,
            parameters.ExpectedMoveCount,
            parameters.ExpectedFetchCount,
            out queryParameters,
            out useNavigationFilter ) )
        throw new DataInternalException();

      // This anonymous method starts the query on the underlying data source provider.
      Func<AsyncCallback, object, IAsyncResult> begin =
        ( cb, st ) => m_dataSourceProvider.BeginExecuteDataQueryInternal( queryParameters, cb, st );

      // This anonymous method returns the query result.
      Func<AsyncResult, IAsyncResult, DependentCallResult> end =
        ( par, iar ) =>
        {
          DataQueryResult dataResult = m_dataSourceProvider.EndExecuteDataQueryInternal( iar );

          // The total count we have obtain might not be the total count
          // we wanted since a navigation filter might have filtered out
          // items we wanted to be included in the count.
          if( useNavigationFilter )
          {
            dataResult.TotalCount = null;
          }

          // Try to get the total items count if we can get it right away.
          if( ( !m_disposed )
            && ( dataResult.TotalCount == null )
            && ( m_dataSourceProvider.CanCount )
            && ( !m_dataSourceProvider.IsAsynchronous ) )
          {
            // This anonymous method start the count query.
            Func<AsyncCallback, object, IAsyncResult> beginCount =
              ( cb, st ) => this.BeginCount( new CountParameters( context ), cb, st );
            // This anonymous returns the items count.
            Func<AsyncResult, IAsyncResult, DependentCallResult> endCount =
              ( p, ar ) =>
              {
                CountResult countResult = this.EndCount( ar );

                if( ( countResult != null ) && ( !countResult.Count.IsEstimate ) )
                {
                  dataResult.TotalCount = countResult.Count.Value;
                }

                return new DependentCallResult( dataResult );
              };

            return new DependentCallResult( null, beginCount, endCount );
          }
          else
          {
            return new DependentCallResult( dataResult );
          }
        };

      asyncResult.Start( begin, end );

      return asyncResult;
    }

    private JumpMoveAndFetchResult<T> EndJumpMoveAndFetchOneWay( JumpMoveAndFetchAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( DataSourceProviderEnumerator<T>.JumpMoveAndFetchOneWayCallId );

      DataQueryResult dataResult = asyncResult.GetCallResult<DataQueryResult>();
      Nullable<long> actualMoveCount = null;
      SourcePosition startPosition = asyncResult.SourcePosition;
      SourcePosition endPosition = asyncResult.SourcePosition;
      RelativeOffset endPositionOffset = null;
      List<T> fetchedItems = ( dataResult.Data != null )
                               ? dataResult.Data.Cast<T>().ToList()
                               : new List<T>( 0 );
      EstimateInt64 totalCount = null;

      // Find out the total count.
      if( dataResult.TotalCount != null )
      {
        totalCount = dataResult.TotalCount;
      }
      // Find out the total count from the inline count.
      else if( ( dataResult.InlineCount.HasValue )
            && ( asyncResult.ExpectedMoveCount != 0 )
            && ( asyncResult.SourcePosition.IsOutOfBound ) )
      {
        totalCount = dataResult.InlineCount.Value;
      }

      // Find out the actual move count and the end position.
      if( fetchedItems.Count > 0 )
      {
        actualMoveCount = asyncResult.ExpectedMoveCount;
        endPosition = new ItemPosition( fetchedItems.Last() );
      }
      else if( asyncResult.ExpectedMoveCount == 0 )
      {
        actualMoveCount = 0;
      }

      bool isHeadingForward = ( Math.Sign( asyncResult.ExpectedMoveCount + asyncResult.ExpectedFetchCount ) >= 0 );

      // Find out the inline count from the last fetched item.
      if( dataResult.InlineCount.HasValue )
      {
        long inlineCount = dataResult.InlineCount.Value;
        long delta = Math.Abs( actualMoveCount.GetValueOrDefault( 0 ) ) + Math.Max( 0, fetchedItems.Count - 1 );

        if( delta > 0 )
        {
          bool exceed = false;

          // Since we are starting out of bound, our calculated inline count need an adjustment.
          exceed |= ( isHeadingForward && startPosition.IsBeforeFirst )
                 || ( !isHeadingForward && startPosition.IsAfterLast );

          // Since a SortPosition may swallow a move to get on the first item, our calculated
          // inline count need an adjustment.
          exceed |= ( asyncResult.ExpectedMoveCount != 0 )
                 && ( startPosition is SortPosition );

          if( exceed )
          {
            delta--;
          }
        }

        ReferencePoint referencePoint = ( isHeadingForward )
                                          ? ReferencePoint.AfterLast
                                          : ReferencePoint.BeforeFirst;
        endPositionOffset = new RelativeOffset( inlineCount - delta, referencePoint );

        // Find out the actual move count using the inline count.
        if( !actualMoveCount.HasValue )
        {
          // It takes one move to get in bound from an out of bound position.
          // A SortPosition may have swallowed a move to get on the first item, the
          // actual move count must be adjusted accordingly.
          int extraMove = 0;
          if( ( totalCount == 0 ) || ( startPosition is SortPosition ) || ( startPosition.IsOutOfBound ) )
          {
            extraMove = 1;
          }

          long moveCount = inlineCount + extraMove;
          actualMoveCount = ( isHeadingForward ) ? moveCount : -moveCount;
        }
      }

      // We must not include the extra item we've fetched to get the end position.
      if( asyncResult.ExpectedFetchCount == 0 )
      {
        fetchedItems.Clear();
      }

      bool successfulMove = ( actualMoveCount.HasValue && actualMoveCount.Value == asyncResult.ExpectedMoveCount );
      bool successfulFetch = ( ( fetchedItems.Count * Math.Sign( asyncResult.ExpectedFetchCount ) ) == asyncResult.ExpectedFetchCount );

      // Find out the end position that must be returned.
      if( !successfulMove || !successfulFetch )
      {
        endPosition = ( isHeadingForward )
                        ? SourcePosition.AfterLast
                        : SourcePosition.BeforeFirst;
      }

      // The data source is empty or we have fetch all items.
      if( ( Math.Abs( asyncResult.ExpectedMoveCount ) == 1 )
        && ( startPosition.IsOutOfBound )
        && ( endPosition.IsOutOfBound )
        && ( startPosition != endPosition ) )
      {
        if( !actualMoveCount.HasValue )
        {
          actualMoveCount = DataUtils.GetUnit( asyncResult.ExpectedMoveCount );
        }

        if( totalCount == null )
        {
          totalCount = fetchedItems.Count;
        }

        Debug.Assert( actualMoveCount == DataUtils.GetUnit( asyncResult.ExpectedMoveCount ) );
        Debug.Assert( totalCount == fetchedItems.Count );
      }

      // Prepare the fetched items collection to return.
      ICollection<T> items = fetchedItems;
      bool isAtEndOfSource = endPosition.IsOutOfBound;

      if( isAtEndOfSource )
      {
        if( fetchedItems.Count <= 0 )
        {
          items = null;
        }

        // Adjust the end position offset since we know exactly what the offset should be.
        ReferencePoint referencePoint = ( endPosition.IsBeforeFirst )
                                          ? ReferencePoint.BeforeFirst
                                          : ReferencePoint.AfterLast;
        endPositionOffset = new RelativeOffset( 0, referencePoint );
      }

      bool success = ( successfulMove )
                  && ( successfulFetch )
                  && ( !isAtEndOfSource );

      return new JumpMoveAndFetchResult<T>(
               asyncResult,
               actualMoveCount,
               items,
               success,
               endPosition,
               endPositionOffset,
               totalCount );
    }

    private bool CreateCountRequest(
      CountParameters parameters,
      out CountQueryParameters queryParameters )
    {
      queryParameters = default( CountQueryParameters );

      // The DataSourceProvider doesn't support counting.
      if( !m_dataSourceProvider.CanCount )
        return false;

      DataSourceContext context = parameters.Context;
      SortPosition startPosition = parameters.StartPosition;

      // Count the number of items in the data source.
      if( startPosition == null )
        return this.CreateCountRequest( context, out queryParameters );

      // Make sure the SortPosition is valid for the current context.
      IEnumerable<SortDescription> currentSortOrder = this.GetCurrentSortOrder( context ).Take( startPosition.SortingReference.Count );
      if( !currentSortOrder.SequenceEqual( startPosition.SortingReference ) )
        throw new DataInternalException();

      if( parameters.TargetGroup )
        return this.CreateCountRequest( context, startPosition.SortValues, out queryParameters );

      return this.CreateCountRequest( context, startPosition.SortValues, startPosition.StartPosition, out queryParameters );
    }

    private bool CreateCountRequest(
      DataSourceContext context,
      out CountQueryParameters queryParameters )
    {
      FilterRequest filterRequest = this.CreateFilterRequest( context.FilterContext, null );

      queryParameters = new CountQueryParameters( null, filterRequest, null, null );

      return true;
    }

    private bool CreateCountRequest(
      DataSourceContext context,
      IList<object> sortValues,
      StartPosition startPosition,
      out CountQueryParameters queryParameters )
    {
      queryParameters = default( CountQueryParameters );

      IList<SortDescription> sortOrder = this.GetCurrentSortOrder( context ).Take( sortValues.Count ).ToList();

      if( sortValues.Count == 0 )
        throw new ArgumentException( "At least one value must be specified.", "sortValues" );

      if( sortOrder.Count != sortValues.Count )
        throw new ArgumentException( "The number of values must be lesser than or equal to the number of fields.", "sortValues" );

      JumpRequest jumpRequest = null;
      SortRequest sortRequest = null;
      FilterExpressionBase filterExpression = null;
      bool startBefore = ( startPosition == StartPosition.Before );

      // Use the jump approch if supported.
      if( m_dataSourceProvider.CanJump )
      {
        JumpStartPosition jumpStartPosition = ( startBefore ) ? JumpStartPosition.Before : JumpStartPosition.After;
        IEnumerable<JumpValue> jumpValues = ( from value in sortValues
                                              select new JumpValue( value ) );

        jumpRequest = new JumpRequest( jumpValues, jumpStartPosition );
        sortRequest = new SortRequest( sortOrder );
      }
      // Convert the jump into a filter.
      else
      {
        // The DataSourceProvider cannot navigate using a filter on the selected properties.
        if( !NavigationFilterUtils.IsSupported( m_dataSourceProvider, sortOrder ) )
          return false;

        filterExpression = NavigationFilterUtils.CreateFilterExpression( sortOrder, sortValues, false, startBefore );
      }

      if( sortRequest != null )
      {
        // Make sure the DataSourceProvider can sort with the requested sort order.
        if( !m_dataSourceProvider.IsSortOrderSupported( sortRequest.SortDescriptions ) )
          return false;
      }

      FilterRequest filterRequest = this.CreateFilterRequest( context.FilterContext, filterExpression );

      queryParameters = new CountQueryParameters( sortRequest, filterRequest, jumpRequest, null );

      return true;
    }

    private bool CreateCountRequest(
      DataSourceContext context,
      IList<object> sortValues,
      out CountQueryParameters queryParameters )
    {
      queryParameters = default( CountQueryParameters );

      IList<SortDescription> sortOrder = this.GetCurrentSortOrder( context ).Take( sortValues.Count ).ToList();

      if( sortValues.Count == 0 )
        throw new ArgumentException( "At least one value must be specified.", "sortValues" );

      if( sortOrder.Count != sortValues.Count )
        throw new ArgumentException( "The number of values must be lesser than or equal to the number of fields.", "sortValues" );

      FilterGroupRequest filterGroupRequest = null;
      SortRequest sortRequest = null;
      FilterExpressionBase filterExpression = null;

      // Use the filter group approch if supported.
      if( m_dataSourceProvider.CanFilterGroup )
      {
        IEnumerable<FilterGroupValue> filterValues = ( from value in sortValues
                                                       select new FilterGroupValue( value ) );

        filterGroupRequest = new FilterGroupRequest( filterValues );
        sortRequest = new SortRequest( sortOrder );
      }
      // Convert the filter group into a filter.
      else
      {
        // The DataSourceProvider cannot navigate using a filter on the selected properties.
        if( !NavigationFilterUtils.IsSupported( m_dataSourceProvider, sortOrder ) )
          return false;

        filterExpression = NavigationFilterUtils.CreateFilterExpression( sortOrder, sortValues );
      }

      if( sortRequest != null )
      {
        // Make sure the DataSourceProvider can sort with the requested sort order.
        if( !m_dataSourceProvider.IsSortOrderSupported( sortRequest.SortDescriptions ) )
          return false;
      }

      FilterRequest filterRequest = this.CreateFilterRequest( context.FilterContext, filterExpression );

      queryParameters = new CountQueryParameters( sortRequest, filterRequest, null, filterGroupRequest );

      return true;
    }

    private bool CreateGroupCountRequest(
      CountParameters parameters,
      out GroupCountQueryParameters queryParameters )
    {
      queryParameters = default( GroupCountQueryParameters );

      SortPosition startPosition = parameters.StartPosition;

      // The SortPosition must be specified in order to know the count level.
      if( startPosition == null )
        throw new DataInternalException();

      // The DataSourceProvider doesn't support group counting.
      if( !m_dataSourceProvider.CanGroupCount )
        return false;

      DataSourceContext context = parameters.Context;
      int groupCountLevel = startPosition.SortingReference.Count;
      IList<string> currentSortOrder = ( from sd in this.GetCurrentSortOrder( context )
                                         select sd.PropertyName ).Take( groupCountLevel ).ToList();

      // Make sure the SortPosition is valid for the current context.
      if( ( groupCountLevel < 0 )
        || ( groupCountLevel > currentSortOrder.Count )
        || ( !currentSortOrder.SequenceEqual( startPosition.SortingReference.Select( ( sd ) => sd.PropertyName ) ) ) )
        throw new DataInternalException();

      IList<object> groupValues = startPosition.SortValues;

      // Count the number of groups in the data source.
      if( groupValues.Count == 0 )
        return this.CreateGroupCountRequest( context, groupCountLevel, out queryParameters );

      if( parameters.TargetGroup )
        return this.CreateGroupCountRequest( context, groupCountLevel, groupValues, out queryParameters );

      return this.CreateGroupCountRequest(
        context.FilterContext,
        startPosition.SortingReference,
        groupValues,
        startPosition.StartPosition,
        out queryParameters );
    }

    private bool CreateGroupCountRequest(
      DataSourceContext context,
      int groupLevelCount,
      out GroupCountQueryParameters queryParameters )
    {
      queryParameters = default( GroupCountQueryParameters );

      IList<SortDescription> sortOrder = this.GetCurrentSortOrder( context ).Take( groupLevelCount ).ToList();

      // Make sure the number of group level requested is valid.
      if( ( groupLevelCount < 0 ) || ( groupLevelCount > sortOrder.Count ) )
        throw new DataInternalException();

      SortRequest sortRequest = new SortRequest( sortOrder );
      FilterRequest filterRequest = this.CreateFilterRequest( context.FilterContext, null );

      queryParameters = new GroupCountQueryParameters( sortRequest, filterRequest, null, null, null, null );

      return true;
    }

    private bool CreateGroupCountRequest(
      DataSourceFilterContext context,
      IList<SortDescription> sortOrder,
      IList<object> groupValues,
      StartPosition startPosition,
      out GroupCountQueryParameters queryParameters )
    {
      queryParameters = default( GroupCountQueryParameters );

      if( sortOrder == null )
        throw new ArgumentNullException( "sortOrder" );

      // Make sure the number of group level requested is valid.
      if( sortOrder.Count == 0 )
        throw new DataInternalException();

      if( groupValues.Count == 0 )
        throw new ArgumentException( "At least one value must be specified.", "groupValues" );

      if( groupValues.Count > sortOrder.Count )
        throw new ArgumentException( "The number of values must be lesser than or equal to the number of fields.", "groupValues" );

      // Make sure the DataSourceProvider can sort with the requested sort order.
      if( !m_dataSourceProvider.IsSortOrderSupported( sortOrder ) )
        return false;

      JumpRequest jumpRequest = null;
      FilterExpressionBase filterExpression = null;
      bool startBefore = ( startPosition == StartPosition.Before );

      // Use the jump approch if supported.
      if( m_dataSourceProvider.CanJump )
      {
        JumpStartPosition jumpStartPosition = ( startBefore ) ? JumpStartPosition.Before : JumpStartPosition.After;
        IEnumerable<JumpValue> jumpValues = ( from value in groupValues
                                              select new JumpValue( value ) );

        jumpRequest = new JumpRequest( jumpValues, jumpStartPosition );
      }
      // Convert the jump into a filter.
      else
      {
        // The DataSourceProvider cannot navigate using a filter on the selected properties.
        if( !NavigationFilterUtils.IsSupported( m_dataSourceProvider, sortOrder ) )
          return false;

        filterExpression = NavigationFilterUtils.CreateFilterExpression( sortOrder, groupValues, false, startBefore );
      }

      SortRequest sortRequest = new SortRequest( sortOrder );
      FilterRequest filterRequest = this.CreateFilterRequest( context, filterExpression );

      queryParameters = new GroupCountQueryParameters( sortRequest, filterRequest, jumpRequest, null, null, null );

      return true;
    }

    private bool CreateGroupCountRequest(
      DataSourceContext context,
      int groupLevelCount,
      IList<object> groupValues,
      out GroupCountQueryParameters queryParameters )
    {
      queryParameters = default( GroupCountQueryParameters );

      IList<SortDescription> sortOrder = this.GetCurrentSortOrder( context ).Take( groupLevelCount ).ToList();

      // Make sure the number of group level requested is valid.
      if( ( groupLevelCount < 0 ) || ( groupLevelCount > sortOrder.Count ) )
        throw new DataInternalException();

      if( groupValues.Count == 0 )
        throw new ArgumentException( "At least one value must be specified.", "groupValues" );

      if( groupValues.Count > sortOrder.Count )
        throw new ArgumentException( "The number of values must be lesser than or equal to the number of fields.", "groupValues" );

      // Make sure the DataSourceProvider can sort with the requested sort order.
      if( !m_dataSourceProvider.IsSortOrderSupported( sortOrder ) )
        return false;

      FilterGroupRequest filterGroupRequest = null;
      FilterExpressionBase filterExpression = null;

      // Use the filter group approch if supported.
      if( m_dataSourceProvider.CanFilterGroup )
      {
        IEnumerable<FilterGroupValue> filterValues = ( from value in groupValues
                                                       select new FilterGroupValue( value ) );

        filterGroupRequest = new FilterGroupRequest( filterValues );
      }
      // Convert the filter group into a filter.
      else
      {
        // The DataSourceProvider cannot navigate using a filter on the selected properties.
        if( !NavigationFilterUtils.IsSupported( m_dataSourceProvider, sortOrder ) )
          return false;

        filterExpression = NavigationFilterUtils.CreateFilterExpression( sortOrder, groupValues );
      }

      SortRequest sortRequest = new SortRequest( sortOrder );
      FilterRequest filterRequest = this.CreateFilterRequest( context.FilterContext, filterExpression );

      queryParameters = new GroupCountQueryParameters( sortRequest, filterRequest, null, filterGroupRequest, null, null );

      return true;
    }

    private bool CreateDataRequest(
      DataSourceContext context,
      SourcePosition startPosition,
      long moveCount,
      int fetchCount,
      out DataQueryParameters queryParameters,
      out bool useNavigationFilter )
    {
      queryParameters = default( DataQueryParameters );
      useNavigationFilter = false;

      if( fetchCount == 0 )
        return false;

      // Get the sort to apply on the data source.
      bool isHeadingForward = ( Math.Sign( moveCount + fetchCount ) >= 0 );
      IList<SortDescription> sortOrder = this.GetCurrentSortOrder( context, !isHeadingForward ).ToList();

      // Make sure the DataSourceProvider can sort with the requested sort order.
      if( !m_dataSourceProvider.IsSortOrderSupported( sortOrder ) )
        return false;

      SortRequest sortRequest = new SortRequest( sortOrder );
      ItemJumpRequest itemJumpRequest = null;
      JumpRequest jumpRequest = null;
      FilterExpressionBase navigationFilterExpression = null;

      if( startPosition.IsOutOfBound )
      {
        // A move counter of 0 while the source position is out of bound should have
        // been handled in the HandleOutOfBoundMove method.  This condition cannot
        // be fixed or handled from here.
        if( moveCount == 0 )
          throw new DataInternalException();

        // We must reduce the move counter by 1 if the source position is currently
        // out of bound.  It takes 1 move to get in bound.
        moveCount += -Math.Sign( moveCount );
      }
      else if( startPosition is ItemPosition )
      {
        ItemPosition itemPosition = ( ItemPosition )startPosition;
        T startingItem = itemPosition.Item as T;

        // A jump cannot be done if no item is specified.
        if( startingItem == null )
          throw new DataInternalException();

        itemJumpRequest = new ItemJumpRequest( startingItem );
      }
      else if( startPosition is SortPosition )
      {
        // The move counter must be adjusted since it takes 1 move to get on the first
        // item.  A move counter of 0 is left unchanged.
        moveCount += -Math.Sign( moveCount );

        SortPosition sortPosition = ( SortPosition )startPosition;
        bool reverse = ( sortPosition.StartPosition == StartPosition.Before ) ^ ( isHeadingForward );

        if( m_dataSourceProvider.CanJump )
        {
          IEnumerable<JumpValue> sortValues = ( from value in sortPosition.SortValues
                                                select new JumpValue( value ) );
          JumpStartPosition jumpStartPosition = ( reverse )
                                                  ? JumpStartPosition.After
                                                  : JumpStartPosition.Before;
          jumpRequest = new JumpRequest( sortValues, jumpStartPosition );
        }
        // Convert the SortPosition into a Filter in order to keep only the desired items.
        else if( !m_dataSourceProvider.IsPrimaryKeyVolatile )
        {
          IList<object> sortValues = sortPosition.SortValues;
          IList<SortDescription> newSortOrder = sortOrder.Take( sortValues.Count ).ToList();

          if( sortValues.Count > newSortOrder.Count )
            throw new DataInternalException();

          bool includeStartingItem = !reverse;

          // The reverse parameter is always false since the sort order given to the method
          // is already reversed if necessary.
          navigationFilterExpression = NavigationFilterUtils.CreateFilterExpression( newSortOrder, sortValues, false, includeStartingItem );
        }
        else
        {
          return false;
        }
      }
      else
      {
        return false;
      }

      SkipRequest skipRequest = null;
      if( moveCount != 0 )
      {
        skipRequest = new SkipRequest( Math.Abs( moveCount ) );
      }

      FilterRequest filterRequest = this.CreateFilterRequest( context.FilterContext, navigationFilterExpression );
      TakeRequest takeRequest = new TakeRequest( Math.Abs( fetchCount ) );

      useNavigationFilter = ( navigationFilterExpression != null );
      queryParameters = new DataQueryParameters(
                          takeRequest,
                          skipRequest,
                          sortRequest,
                          filterRequest,
                          itemJumpRequest,
                          jumpRequest );

      return true;
    }

    private bool CreateStatRequest(
      DataSourceContext context,
      StatFunction statFunction,
      out StatQueryParameters queryParameters )
    {
      FilterRequest filterRequest = this.CreateFilterRequest( context.FilterContext, null );
      StatRequest statRequest = new StatRequest( statFunction );

      queryParameters = new StatQueryParameters( statRequest, null, filterRequest, null );

      return true;
    }

    private bool CreateStatRequest(
      DataSourceContext context,
      StatFunction statFunction,
      IList<object> sortValues,
      out StatQueryParameters queryParameters )
    {
      queryParameters = default( StatQueryParameters );

      IList<SortDescription> sortOrder = this.GetCurrentSortOrder( context ).Take( sortValues.Count ).ToList();

      if( sortValues.Count == 0 )
        throw new ArgumentException( "At least one value must be specified.", "sortValues" );

      if( sortOrder.Count != sortValues.Count )
        throw new ArgumentException( "The number of values must be lesser than or equal to the number of fields.", "sortValues" );

      FilterGroupRequest filterGroupRequest = null;
      SortRequest sortRequest = null;
      FilterExpressionBase filterExpression = null;

      // Use the filter group approch if supported.
      if( m_dataSourceProvider.CanFilterGroup )
      {
        IEnumerable<FilterGroupValue> filterValues = ( from value in sortValues
                                                       select new FilterGroupValue( value ) );

        filterGroupRequest = new FilterGroupRequest( filterValues );
        sortRequest = new SortRequest( sortOrder );
      }
      // Convert the filter group into a filter.
      else
      {
        // The DataSourceProvider cannot navigate using a filter on the selected properties.
        if( !NavigationFilterUtils.IsSupported( m_dataSourceProvider, sortOrder ) )
          return false;

        filterExpression = NavigationFilterUtils.CreateFilterExpression( sortOrder, sortValues );
      }

      if( sortRequest != null )
      {
        // Make sure the DataSourceProvider can sort with the requested sort order.
        if( !m_dataSourceProvider.IsSortOrderSupported( sortRequest.SortDescriptions ) )
          return false;
      }

      FilterRequest filterRequest = this.CreateFilterRequest( context.FilterContext, filterExpression );
      StatRequest statRequest = new StatRequest( statFunction );

      queryParameters = new StatQueryParameters( statRequest, sortRequest, filterRequest, filterGroupRequest );

      return true;
    }

    private IEnumerable<SortDescription> GetCurrentSortOrder( DataSourceContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      return context.GetCurrentCompleteSortOrder();
    }

    private IEnumerable<SortDescription> GetCurrentSortOrder( DataSourceContext context, bool reverse )
    {
      foreach( SortDescription sortDescription in this.GetCurrentSortOrder( context ) )
      {
        if( reverse )
        {
          ListSortDirection direction;

          if( sortDescription.Direction == ListSortDirection.Ascending )
          {
            direction = ListSortDirection.Descending;
          }
          else
          {
            direction = ListSortDirection.Ascending;
          }

          yield return new SortDescription( sortDescription.PropertyName, direction );
        }
        else
        {
          yield return sortDescription;
        }
      }
    }

    private AsyncCallback GetMiddleTierCallback( AsyncCallback callback )
    {
      if( callback == null )
        return null;

      return ( IAsyncResult asyncResult ) =>
        {
          if( !m_disposed )
          {
            callback.Invoke( asyncResult );
          }
        };
    }

    private IEnumerable<DataSourceChangedEventArgs> TransformDataSourceChangedEventArgsItemsAdded(
      DataSourceFilterContext context,
      DataSourceChangedEventArgs e )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      Debug.Assert( e.Action == DataSourceChangedAction.Add );

      if( ( e.NewItems == null ) || ( e.NewItems.Count == 0 ) )
        return Enumerable.Empty<DataSourceChangedEventArgs>();

      List<DataSourceChangedEventArgs> newEventArgs = new List<DataSourceChangedEventArgs>( 1 );

      Func<T, bool> predicate = this.CreateFilterPredicate( context );

      if( predicate != null )
      {
        List<object> items = ( from item in e.NewItems.Cast<T>()
                               where predicate.Invoke( item )
                               select ( object )item ).ToList();

        if( items.Count == e.NewItems.Count )
        {
          newEventArgs.Add( e );
        }
        else if( items.Count > 0 )
        {
          newEventArgs.Add( new DataSourceChangedEventArgs( DataSourceChangedAction.Add, items, null ) );
        }
      }
      else
      {
        newEventArgs.Add( e );
      }

      return newEventArgs;
    }

    private IEnumerable<DataSourceChangedEventArgs> TransformDataSourceChangedEventArgsItemsRemoved(
      DataSourceFilterContext context,
      DataSourceChangedEventArgs e )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      Debug.Assert( e.Action == DataSourceChangedAction.Remove );

      if( ( e.OldItems == null ) || ( e.OldItems.Count == 0 ) )
        return Enumerable.Empty<DataSourceChangedEventArgs>();

      List<DataSourceChangedEventArgs> newEventArgs = new List<DataSourceChangedEventArgs>( 1 );

      Func<T, bool> predicate = this.CreateFilterPredicate( context );

      if( predicate != null )
      {
        List<object> items = ( from item in e.OldItems.Cast<T>()
                               where predicate.Invoke( item )
                               select ( object )item ).ToList();

        if( items.Count == e.OldItems.Count )
        {
          newEventArgs.Add( e );
        }
        else if( items.Count > 0 )
        {
          newEventArgs.Add( new DataSourceChangedEventArgs( DataSourceChangedAction.Remove, null, items ) );
        }
      }
      else
      {
        newEventArgs.Add( e );
      }

      return newEventArgs;
    }

    private IEnumerable<DataSourceChangedEventArgs> TransformDataSourceChangedEventArgsItemsReplaced(
      DataSourceFilterContext context,
      DataSourceChangedEventArgs e )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      Debug.Assert( e.Action == DataSourceChangedAction.Replace );
      Debug.Assert( e.NewItems.Count == e.OldItems.Count );

      if( e.OldItems.Count == 0 )
        return Enumerable.Empty<DataSourceChangedEventArgs>();

      List<DataSourceChangedEventArgs> newEventArgs = new List<DataSourceChangedEventArgs>();

      Func<T, bool> predicate = this.CreateFilterPredicate( context );

      if( predicate != null )
      {
        List<object> itemsAdded = new List<object>();
        List<object> itemsRemoved = new List<object>();
        List<object> itemsReplacedOld = new List<object>();
        List<object> itemsReplacedNew = new List<object>();
        int count = e.NewItems.Count;

        for( int i = 0; i < count; i++ )
        {
          T oldItem = ( T )e.OldItems[ i ];
          T newItem = ( T )e.NewItems[ i ];

          bool isOldItemAccepted = true;
          bool isNewItemAccepted = true;

          if( predicate != null )
          {
            isOldItemAccepted = predicate.Invoke( oldItem );
            isNewItemAccepted = predicate.Invoke( newItem );
          }

          // The item has been replaced.
          if( isOldItemAccepted && isNewItemAccepted )
          {
            itemsReplacedOld.Add( oldItem );
            itemsReplacedNew.Add( newItem );
          }
          // The item has been removed.
          else if( isOldItemAccepted )
          {
            itemsRemoved.Add( oldItem );
          }
          // The item has been added.
          else if( isNewItemAccepted )
          {
            itemsAdded.Add( newItem );
          }
        }

        if( itemsReplacedOld.Count == count )
        {
          newEventArgs.Add( e );
        }
        else
        {
          if( itemsReplacedOld.Count > 0 )
          {
            newEventArgs.Add( new DataSourceChangedEventArgs( DataSourceChangedAction.Replace, itemsReplacedNew, itemsReplacedOld ) );
          }

          if( itemsAdded.Count > 0 )
          {
            newEventArgs.Add( new DataSourceChangedEventArgs( DataSourceChangedAction.Add, itemsAdded, null ) );
          }

          if( itemsRemoved.Count > 0 )
          {
            newEventArgs.Add( new DataSourceChangedEventArgs( DataSourceChangedAction.Remove, null, itemsRemoved ) );
          }
        }
      }
      else
      {
        newEventArgs.Add( e );
      }

      return newEventArgs;
    }

    private IEnumerable<DataSourceChangedEventArgs> TransformDataSourceChangedEventArgsItemsChanged(
      DataSourceChangedEventArgs e )
    {
      Debug.Assert( e.Action == DataSourceChangedAction.ItemChanged );

      if( ( e.ItemsChanged == null ) || ( e.ItemsChanged.Count == 0 ) )
        return Enumerable.Empty<DataSourceChangedEventArgs>();

      return new DataSourceChangedEventArgs[] { e };
    }

    private Func<T, bool> CreateFilterPredicate( DataSourceFilterContext context )
    {
      if( ( context == null )
        || ( context.Filter == null && context.FilterExpression == null ) )
        return null;

      Func<T, bool> filterPredicate = null;
      if( context.Filter != null )
      {
        Predicate<object> filter = context.Filter;
        filterPredicate = ( T item ) => filter.Invoke( item.Data );
      }

      Func<T, bool> filterExpressionPredicate = null;
      if( context.FilterExpression != null )
      {
        PredicateBuilder pb = new PredicateBuilder( context.FilterExpression );
        Func<object, bool> newFilterExpressionPredicate = pb.ToPredicate<object>(
                                                            m_dataSourceProvider.ElementType,
                                                            m_dataSourceProvider.GetElementMemberType );

        filterExpressionPredicate = ( T item ) => newFilterExpressionPredicate.Invoke( item );
      }

      if( filterPredicate == null )
      {
        return filterExpressionPredicate;
      }
      else if( filterExpressionPredicate == null )
      {
        return filterPredicate;
      }
      // Create a predicate that is combining the two predicates.
      else
      {
        return ( T item ) => filterPredicate.Invoke( item )
                          && filterExpressionPredicate.Invoke( item );
      }
    }

    private FilterRequest CreateFilterRequest(
      DataSourceFilterContext context,
      FilterExpressionBase filterExpression )
    {
      Predicate<object> localPredicate = context.Filter;
      FilterExpressionBase remotePredicate = context.FilterExpression;

      if( filterExpression != null )
      {
        // Combine the filter expressions.
        if( remotePredicate != null )
        {
          remotePredicate = new AndFilterExpression( remotePredicate, filterExpression );
        }
        else
        {
          remotePredicate = filterExpression;
        }
      }

      // There is no filter.
      if( ( localPredicate == null ) && ( remotePredicate == null ) )
        return null;

      return new FilterRequest( localPredicate, remotePredicate );
    }

    #region IDisposable Members

    protected override void Dispose( bool disposing )
    {
      m_disposed = true;

      base.Dispose( disposing );
    }

    #endregion

    #region Private Fields

    private readonly DataSourceProvider m_dataSourceProvider;
    private bool m_disposed; //false

    #endregion
  }
}
